package com.klikmee.sms.core.commons.events;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import org.apache.log4j.Logger;

/**
 * Class that keeps track of app events and their listeners. Registers listeners
 * for particular app event and notifies them when that event occurs.
 * 
 * @author makcro
 * 
 */
public class Dispatcher implements EventDispatcher {

	/**
	 * Logger instance
	 */
	private Logger logger = Logger.getLogger(Dispatcher.class);
	/**
	 * Map of event types and their registered listeners
	 */
	private HashMap<String, Set<EventListener>> eventListenerMap = new HashMap<String, Set<EventListener>>();

	/**
	 * Private instance of dispatcher object
	 */
	private static EventDispatcher INSTANCE;

	private Dispatcher(){
		
	}
	
	public void  init() {
		logger.info( MessageFormat.format( "Dispatcher initialized with [{0}] event registred", eventListenerMap.size() ) );
	}
	/**
	 * Getter for dispatcher instance
	 * 
	 * @return
	 */
	public static EventDispatcher get() {

		if (INSTANCE == null) {
			INSTANCE = new Dispatcher();
		}

		return INSTANCE;
	}

	/* (non-Javadoc)
	 * @see com.klikmee.sms.core.commons.events.EventDispatcher#registerListener(java.lang.String, com.klikmee.sms.core.commons.events.EventListener)
	 */
	@Override
	public void registerListener(String eventType, EventListener listener) {
		// check if this event type is not already mapped
		if (!eventListenerMap.containsKey(eventType)) {
			eventListenerMap.put(eventType, new HashSet<EventListener>());
		}
		eventListenerMap.get(eventType).add(listener);
	}

	public void setEventListenerMap(HashMap<String, Set<EventListener>> eventListenerMap) {
		this.eventListenerMap = eventListenerMap;
	}

	public HashMap<String,Set<EventListener>> getEventListenerMap() {
		return eventListenerMap;
	}

	/* (non-Javadoc)
	 * @see com.klikmee.sms.core.commons.events.EventDispatcher#dispacth(com.klikmee.sms.core.commons.events.AppEvent, boolean)
	 */
	@Override
	public void dispatch(final AppEvent appEvent, boolean inSameThread) {
		if (inSameThread) {
			logger.debug( MessageFormat.format( "Dispatching event of type [{0}] in the same thread", appEvent.getEventType() ) );
			performDispatch(appEvent);
		}

		else {

			Thread dispatchThread = new Thread(new Runnable() {

				@Override
				public void run() {
					logger.debug( MessageFormat.format( "Dispatching event of type [{0}] in a new thread", appEvent.getEventType() ) );
					performDispatch(appEvent);

				}
			}, "Dispatcher thread");

			dispatchThread.start();
		}
	}

	public void dispatch(AppEvent appEvent) {
		this.dispatch(appEvent, false);
	}

	/**
	 * Does actual dispatching of the specified event. Iterates through set of
	 * listeners and calls {@link EventListener#handleEvent(AppEvent)} on each.
	 * 
	 * @param appEvent
	 *            event to be dispatched
	 */
	private void performDispatch(AppEvent appEvent) {
		String eventType = appEvent.getEventType();
		// check if there are registered listeners for this event
		if (eventListenerMap.containsKey(eventType)) {
			Set<EventListener> listeners = eventListenerMap.get(eventType);
			logger.debug( MessageFormat.format( "Found [{0}] listeners for event type [{1}]", listeners.size(), appEvent.getEventType() ) );
			
			for (EventListener eventListener : listeners) {
				eventListener.handleEvent(appEvent);
			}
		}
	}
}
